쿠버 RBAC

개요

쿠버네티스에서 주로 사용하는 쿠버네티스 인가 방식 중 하나이다.
RBAC는 역할을 먼저 만들고, 이 역할이 할 수 있는 일을 할당한다.
그 이후 이 역할을 각 주체에게 씌우는 방식으로 접근을 제어한다.
그래서 역할에 맞게 리소스에 접근하고 실행할 수 있게 할 수 있다.

RBAC 모드는 쿠버에서 거의 표준으로 지정하는 인가 모드로, 인가를 위한 각종 개념들도 RBAC에 기반하고 있다.
그래서 아예 내장 api로서 RBAC를 제공해준다.
실질적으로 쿠버네티스에서 인가를 관리한다고 하면 이 RBAC를 이야기하는 경우가 많다.
다른 놈들은 저마다 목적이 명확하거나 하자가 있기 때문..
자세한 건 쿠버네티스 인가 쪽을 확인하자.

구조

이와 관련된 오브젝트만 4개인데, 크게 두 가지 기준으로 4가지로 분화됐다.

처음 이해할 때 팍 와닿는 것은 각각의 쓰임새를 토대로 먼저 Role, Binding 두 유형으로 분류하는 것이다.

이러한 유형 분류 속에, 클러스터 전역적인 설정인지 특정 네임스페이스에 대한 설정인지로 클롤과 롤, 클롤바와 롤바로 나뉜 것이다.

그렇다면 위의 그림처럼 이들을 사용할 때 어떤 식의 동작이 이뤄지는지 간단히 예시를 들어보겠다.

The ClusterRoleBinding "clusterrolebinding-role" is invalid: roleRef.kind: Unsupported value: "Role": supported values: "ClusterRole"

마지막 케이스는 생각을 해봐도 사실 그다지 이치에 맞지 않을 것이다.
클러스터롤바인딩이란 클러스터 전역에서 사용할 수 있는 동작을 매핑시키는 행위인데 이걸 특정 네임스페이스의 롤과 매핑을 시킨다는 것 자체가 이상하다.
그래서 위처럼 에러가 발생하는 것을 확인할 수 있다.

또 하나 유심히 볼 것은 클러스터롤-롤바인딩과 롤-롤바인딩이 결국 유저에게 주어진 권한이 같다는 것이다.
실제로도 그러한데, 전자의 경우는 나중에 같은 동작을 다른 네임스페이스에서도 롤바인딩을 하는데 사용할 수 있다는 점이 다르다.
그래서 네임스페이스 종속적으로 설정할 필요는 있는데, 여러 네임스페이스에 대해서 지정할 때 유용한 방식이다.

양식 작성법

유형 별로 오브젝트 작성법을 묶어 정리하겠다.

Roles

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: pod-reader
rules:
- apiGroups: [""]
  resources: ["pods", "pods/log", "services"]
  resourceName: ["name"]
  verbs: ["get", "watch", "list"]
 - apiGroups: ["apps"]
  resources: ["deployments"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] 
---
kind: ClusterRole
metadata:
  name: monitoring-endpoints
rules:
- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["get"]
- nonResourceURLs: ["/healthz", "/healthz/*"] 
  verbs: ["get", "post"]

롤은 메타데이터에 네임스페이스를 지정해주어야 하고, 반면 클롤은 오히려 네임스페이스가 없어야 한다.

rules

.rules 필드에 허용하고자 하는 동사와 리소스를 리스트로 써주면 된다.
GET /api/v1/namespaces/{namespace}/pods/{name}/log 어떤 리소스를 정의할 때는 이런 api 경로를 참고하면 된다.
즉, apiGroups에는 어떤 api 그룹인지, resources에는 어떤 리소스 유형인지를 적으면 된다.
(헷갈리면 쿠버네티스 API 구조 참고)
위에 pods/log와 같은 식으로 서브 리소스를 명시하는 방식도 가능하다.
개별적으로 작성하기 싫다면 그냥 *로 와일드카드로 사용해도 된다.

리소스 중에서 특정한 놈만 명시하고 싶다면 resourceName을 사용하면 되겠다.
솔직히 이 방식은 그다지 유용하다고 느끼지는 않는 게, 어떻게 일일히 이렇게 지정을 하냐..
차라리 이것도 라벨 셀렉터 방식을 지원했으면 하는 바람이 있다.

resourceName 유의사항

create, delelteCollection의 경우에는 resourceName을 제한할 수 없다.
그리고 list, watch에 대해서 resourceName을 사용했다면, 이 역할을 받은 유저는 Field Selector를 사용해 매칭해야 한다.
kubectl get configmaps --field-selector=metadata.name=my-configmap 이런 식으로 말이다.

클롤의 경우에는 클러스터 범위의 리소스를 지정하는 것도 가능하다.
대표적인 것이 네임스페이스, 노드 같은 것들이 있다.
추가적으로, 클롤은 리소스가 아닌 비리소스에 대한 동작도 지정할 수 있는데 nonResourceURLs를 이용해 작성하면 된다.
그래서 클롤의 사용례 다음과 같이 정리할 수 있다.

  1. 각 네임스페이스 모두에 적용될만한 권한을 정의할 때
  2. 특정 네임스페이스의 리소스를 모든 네임스페이스에 허용할 때
  3. 클러스터 전역의 리소스를 설정할 때

aggregationRule

클러스터롤의 경우 여러 클러스터롤을 한꺼번에 결합하는 기능을 제공한다.

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: monitoring
aggregationRule:
  clusterRoleSelectors:
  - matchLabels:
      rbac.example.com/aggregate-to-monitoring: "true"
rules: [] # 여기는 채워도 덮어씌워지니 빈 값으로 넣어야 한다.

컨트롤러가 aggregationRule라벨 셀렉터로 매칭된 클롤들을 하나의 클러스터롤로 엮이도록 추적하고 관리해준다.
위의 케이스라면, monitoring이라는 클러스터롤은 저 라벨에 해당하는 클러스터롤의 규칙을 받게 된다.
이 방식의 장점은 클러스터롤을 동적으로 관리할 수 있다는 것이다.

RoleBindings

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-secrets
  # 클러스터롤에 대해 바인딩할 때도 항상 네임스페이스를 신경써야 한다.
  namespace: development
subjects:
- kind: User
  name: dave@test.com
  apiGroup: rbac.authorization.k8s.io
- kind: ServiceAccount
  name: default 
  namespace: kube-system
roleRef:
  kind: ClusterRole
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: read-secrets-global
subjects:
- kind: Group
  name: manager 
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io

바인딩을 할 때도 마찬가지로 롤바는 네임스페이스를 신경써서 넣어줘야 한다.
roleRef 필드에 클롤이나 롤을 넣어주면 되는데, 위에서도 말했든 클롤바의 경우 롤을 넣을 수 없단 것을 유의하자.

subjects

이 부분이 어떤 주체가 해당 롤을 받을지를 지정하는 부분이다.
유저와 그룹 이름을 넣을 때는 단순한 문자열로 넣는다고 생각하면 된다.
해당 문자는 이메일 형식도 되고 어떤 형식이든 되는데, 이건 쿠버네티스 인증 단계에서 어떻게 넘겨주냐에 따라 결정될 것이다.
참고로 system: 접두사는 쿠버네티스 시스템에 예약돼있으니, 가급적 일반 유저 앞에 붙이지 않게 인증 단계에서 잘 설정하자.
괜히 복잡성만 증가할 것이다.

서비스 어카운트를 적을 때는 네임스페이스를 같이 명시해줘야 한다.

subjects:
- kind: Group
  name: system:serviceaccounts:qa
  apiGroup: rbac.authorization.k8s.io

근데 서비스 어카운트를 유저나 그룹으로 명시하는 방법도 있다.
유저로서 명시하고 싶으면 system:serviceaccount:{네임스페이스}:{서비스어카운트} 과 같은 식으로 작성한다.
그룹으로 명시하고 싶다면 system:serviceaccounts:{네임스페이스}처럼 해주면 된다.
그룹일 때 네임스페이스 부분을 빼버리면 그냥 모든 서비스 어카운트에 대해 적용한다는 뜻과 같다.

문득 궁금증이 생겨 T-각 네임스페이스 서비스 어카운트에 권한 바인딩 실험을 진행했다.

유의사항

한번 roelRef에 명시되어 바인딩된 역할은 수정될 수 없다.
그래서 변경하고 싶으면 지웠다가 다시 만들어야 한다.

여기에는 두 가지 이유가 있다.
roleRef를 불변하게 만들면, 바인된 오브젝트를 업데이트할 수 있게 만든다.
그래서 주체 목록을 관리하기 용이하다.
롤 자체를 변경하지않고!

다른 역할을 바인딩하는 것은 근본적으로 다른 바인딩이다.
roleRef 부분을 바꾸기 위해 완전히 지웠다가 다시 만들게 하는 것은 의도된 모든 주체가 제대로된 역할에 바인딩되는 것을 보장해준다.

kubectl auth reconcile을 통해 만들고 업데이트를 할 수 있다.
이걸로 지우고 만드는 작업을 할 수 잇다.
사용은 apply와 비슷한데, 네임스페이스나 롤이 없으면 아예 만들어서 준다.
--remove-extra-subjects, --remove-extra-permissions을 넣으면 제시된 파일 내에 명시되지 않은 권한이나 주체가 전부 제거된다.
가령 a라는 롤에 바인딩이 1,2 유저가 각각 다른 바인딩으로 돼잇었다해보자.
근데 reconcile로 바인딩을 1유저만 되어있는 파일을 넣었따면? 2가 지워지나?

클러스터에 기본 세팅된 롤과 바인딩

클러스터에 기본적으로 만들어진 클러스터롤과 바인딩 오브젝트가 존재한다.
이것들은 클러스터가 초기 구축됐을 때 설정돼 있으며, 클러스터를 운영할 때 있어서 다양한 요소들을 편하게 관리하기 위해 존재한다.
image.png
대부분은 system:이라는 이름이 접두사로 붙어 쉽게 판별이 가능하다.

labels:
    kubernetes.io/bootstrapping: rbac-defaults

또한 이들은 kubernetes.io/bootstrapping=rbac-defaults 라벨이 붙는다.

이들에는 일단 자동 조정(auto reconcilationi) 기능이 붙어있다.
image.png
이들을 우리가 수정하더라도, api서버가 다시 기동될 때 해당 오브젝트들을 조회한 후 알아서 기본으로 업데이트를 해버린다.

rbac.authorization.kubernetes.io/autoupdate: "true"

구체적으로는 위의 어노테이션을 보고 추적하는 건데, 이런 방식은 두가지 이점이 있다.
일단 관리자 미숙으로 클러스터에 사고가 났을 때, 자동으로 회복될 수 있도록 해준다.
두번째로 클러스터 버전을 올릴 때 알아서 새로운 api그룹이나 버전에 맞게 인가 체크가 될 수 있게 해준다.

rbac.authorization.kubernetes.io/autoupdate: false로 하면 자동 조정이 일어나지 않을 것이다.
그래서 임의로 수정을 하고 싶은 경우에는 꼭 이 어노테이션을 수정해주자!

이제 기본으로 세팅된 몇 가지 유형의 롤들을 살펴보자.

api 디스커버리 롤

모든 주체는 CRD까지 포함해서 클러스터에서 사용가능한 리소스 전부를 조회할 수 있도록 기본 롤과 바인딩이 설정돼있다.
정확하게는 1.14버전까지는 인증되지 않은 주체도 가능했는데, 이후부터는 인증돼야만 디스커버리를 할 수 있다.
--anonymous-auth=false를 통해 익명 유저에 대해서는 비활성화하는 것이 가능하다.

다음이 api 탐색을 위한 클러스터롤이다.

쿠버의 안정성

왜 굳이 인증되지 않은 유저가 사용가능한 api 리소스들을 조회할 수 있어야 하는가?
어쩌면 아직 자신의 인증 정보를 알지 못하는 유저가 자신이 뭘할 수 있는지 알기 위함일지도 모른다.
그러나 나는 오히려 이것이 보안 위협을 초래한다고 생각한다.
오히려 어떤 유저가 어떤 권한과 동작을 할 수 있는지를 제공해주는 api를 따로 제공하는 것이 좋다고 생각한다.
오죽하면 kubectl로 관련한 플러그인이 나올까.

사용자 중심 롤(user-facing role)

근데 몇몇 기본 클롤은 또 system:이 접두사로 붙지 않는데, 이것들은 사용자가 사용하도록 의도된 롤이다.
이 클롤들은 위에서 본 [[#aggregationRule]]로 구성돼 있어서, 새로운 api가 추가되더라도 동적으로 접근 권한이 설정된다.

    rbac.authorization.k8s.io/aggregate-to-admin: "true"
    rbac.authorization.k8s.io/aggregate-to-edit: "true"
    rbac.authorization.k8s.io/aggregate-to-view: "true"

수동으로 추가하고 싶다면 직접 만든 클롤에다 이런 라벨을 달아주자.

다음이 유저 중심 클러스터롤이다.

이밖의 클러스터를 위한 롤

이밖에도 여러가지 롤이 존재한다.
전부 열거하지 않을 거고, 혹시 확인이 필요하다면 직접 참고하자.[1]

권한 상승 방지(privilege escalation prevention)

RBAC 리소스는 인가 설정을 책임지기에, 권한 없는 유저가 단순히 RBAC 리소스에 대한 권한을 가진다고 해서 함부로 권한을 추가적으로 얻게 되는 것을 막아준다.
이건 api 서버 단에서 막아주는 방식이라 RBAC 모드를 사용하지 않는다고 하더라도 강제 적용된다.

쉽게 이해하기 위해 예를 든다.
a 라는 유저가 있는데, 이 유저는 롤과 롤바에 대한 create 권한만을 가지고 있다.
이 유저는 스스로 파드를 만들 권한을 취득할 수 있을까?
그럴 수 없다가 정답이다.

롤에 대한 권한이 있더라도 다음의 조건이 충족돼야만 주체는 마음대로 다른 리소스에 대한 추가 권한을 획득할 수 있다.

이건 바인딩을 할 때도 마찬가지로, 롤바에 대해 bind 동사 권한을 가지고 있지 않다면 맘대로 바인딩을 만들 수 없다.
그래서 롤과 롤바에 대한 권한을 가지고 있더라도, 주체가 함부로 다른 권한까지 취득할 수 있는 것은 아니다.

정리하자면, 주체는 롤에 대해 escalate 동사, 바인딩에 대해 bind 동사를 가지지 않는 한 자신이 이미 가진 범위 이상의 권한을 획득할 수 없다.

문서에선..

문서에서는 암시적인 방법과 명시적인 방법을 구분한다.
암시적인 방법은 주체에게 주체가 가지고자 하는 관련 권한을 관련한 권한을 가진 다른 관리자가 주는 것을 말한다.
근데 이걸 권한 상승이란 맥락을 설명하기 위한 구절에서 불필요한 설명이라 생각해 생략했다.

그럼, 클러스터 부트스트랩이 일어날 당시에, 각종 기본 세팅된 롤이나 바인딩은 대체 어떻게 만들어질 수 있었던 것인가?!
하는 궁금증이 생긴 사람 아주 칭찬합니다.
이때는 system:masters 그룹의 신원이 사용되는데 위에서도 봤듯이 이 그룹은 RBAC에 대한 어떠한 제약도 받지 않는다..
이 그룹이 cluster-admin 이란 슈우우우우퍼 유저에게 바인딩돼서 각종 작업이 일어나게 되는 것이다.
클러스터를 설치한 초기 관리자가 가지는 권한도 바로 이것으로, 이것은 클러스터가 제대로 구축된 다음에는 사용하지 않는 것이 바람직할 것이다.
E-쿠버 RBAC 권한 상승 방지 실습에서 실습을 진행했다.

reconcile

kubectl을 이용할 때 auth reconcile을 이용해 권한을 업데이트하는데 사용할 수 있다.
reconcile은 말 그대로 재조정을 하는 것이다.

바인딩 오브젝트는 원래 roleRef 필드를 변경할 수 없도록 돼있다.
그래서 이 명령어를 제공하는 건데, k auth reconcile -f {파일}.yaml과 같은 식으로 해주면 해당 부분이 업데이트 반영된다.
만약 없는 네임스페이스에 대해 롤바를 한다면, 이걸 이용하면 알아서 없는 네임스페이스가 생성된다.

이 명령어가 추가적으로 좋은 것은 정확하게 인가 관련 오브젝트만 건드린다는 것이다.
가령 여러 양식이 한꺼번에 작성된 파일이어도 이걸 이용하면 인가 관련 오브젝트에 대해서만 변경사항이 적용된다.[2]

또한 --remove-extra-permissions, --remove-extra-subjects를 인자를 추가적으로 넣으면 이전에 오브젝트를 만들 때 들어갔던 권한과 주체 정보를 안전하게 변경할 수도 있다.

서비스 어카운트 권한

기본 rbac 정책은 컨트롤 플레인 컴포넌트, 노드, 컨트롤러에 대한 권한을 부여한다.
kube-namespace 바깥의 서비스 어카운트에 대해서는 기본 권한이 아무것도 없다.
그렇기에 직접 서비스 어카운트를 만들었다면 직접 특정 롤을 부여해야 한다.
보안을 신경 쓰면서 권한을 줄 때는 이렇게 하자.

엔드포인트슬라이스 쓰기 권한

1.22 이후로, 엔드포인트슬라이스에 대해 기본적으로 쓰기 권한이 [[#aggregationRule]]로 제공되지 않는다.
이게 허용됐다가는 엔포슬을 이용해 클러스터로 들어온 트래픽에 대해 임의의 악성 서버로 보내버릴 수 있기 때문이다.[3]
구체적으로 이런 상황을 생각해볼 수 있다.
이미 존재하는 서비스가 있고, 여기에 엔드포인트가 연결됐다.
내부 공격자가 임의로 해당 서비스에 연결될 커스텀 엔드포인트 오브젝트를 만드는 것이 가능하다.
이 엔드포인트는 임의의 ip를 가리킬 수 있기 때문에, 여차하면 해당 서비스에 접근하는 트래픽은 악성 백엔드로 보내질 수 있게 된다.
멀티 테넌시 환경에서 이러한 공격은 시끄러운 이웃 문제를 넘어서 이웃에 대한 직접적인 공격을 가능하게 만든다.
이 공격은 네트워크 폴리시을 이용해서도 막을 수 없기 때문에, 쿠버네티스에서는 아예 [[#사용자 중심 롤(user-facing role)]]에서 엔드포인트 관련 권한을 제거시켰다.

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  annotations:
    kubernetes.io/description: |-
      Add endpoints write permissions to the edit and admin roles. This was
      removed by default in 1.22 because of CVE-2021-25740. See
      https://issue.k8s.io/103675. This can allow writers to direct LoadBalancer
      or Ingress implementations to expose backend IPs that would not otherwise
      be accessible, and can circumvent network policies or security controls
      intended to prevent/isolate access to those backends.
      EndpointSlices were never included in the edit or admin roles, so there
      is nothing to restore for the EndpointSlice API.      
  labels:
    rbac.authorization.k8s.io/aggregate-to-edit: "true"
  name: custom:aggregate-to-edit:endpoints # you can change this if you wish
rules:
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["create", "delete", "deletecollection", "patch", "update"]

굳이 엔드포인트에 대한 쓰기 권한을 주고 싶다면, 이런 식으로 커스텀해서 클롤을 만들어주자.

ABAC로부터 업그레이드

과거의 쿠버네티스는 쿠버네티스 인가를 사용하여, 서비스 어카운트에 많은 권한을 주었다고 한다.
그렇기에 과거의 클러스터로부터 현재 버전으로 업그레이드할 때는, ABAC의 권한을 없애고 RBAC로 마이그레이션하길 권장하고 있다.
어차피 인가 모드는 여러 개를 합쳐서 쓸 수 있기 때문에, 일단 두 모드를 활성화 해둔 상태로 인가 과정을 모니터링해주면 쉽게 상황을 확인할 수 있다.
RBAC가 아닌 ABAC로 인가되는 동작들을 매번 체크하고, 관련한 동작들에 대해 RBAC 오브젝트를 만들어주면 된다.
모든 작업이 끝나면 ABAC 모드를 제거해주면 된다.

이 내용은 여기에 잠시 정리

RBAC 모범 사례

RBAC를 적절하게 사용하기 위한 가이드 역시 문서에서 제공되고 있다.[4]
(근데 위치가 왜 컨셉 안에 있을까)
권한을 디자인할 때 관리자는 권한 상승이 일어날 수 있는 영역을 이해하고 주의할 필요가 있다.
이에 대한 전반적인 위협 요소와, 적절한 가이드라인을 정리해보자.
그로부터 과도한 접근으로 인한 보안 사고를 줄여야 한다.

기본

권한 상승 위협

위에서 [[#권한 상승 방지(privilege escalation prevention)]]로 bindescalate 동사의 위험성을 보았다.
이것을 포함해, 권한을 상승시킬 수 있는 여지가 있는 위협들이 많다.

워크로드 폭격기(서비스 거부)

클러스터는 결국 자원을 효율적으로 쓰기 위해 구축된다.
근데 임의의 유저가 무언가를 만들 권한이 있기라도 한다면, 일단 이 유저는 api서버에 디도스 마냥 요청을 때려버리는 수가 있다.
api 서버가 잘 버텼다고 쳐도, 오브젝트를 마구 만들면 이 정보가 쌓이는 Etcd가 감당하지 못할 가능성이 있다.
그게 아니더라도 클러스터의 전체 자원를 부족하게 만드는 것도 가능하니 주의해야 한다.
ResourceQuota를 설정해 아예 만들 수 있는 오브젝트 개수를 제한해버리는 게 좋다.

참고


  1. https://kubernetes.io/docs/reference/access-authn-authz/rbac/#core-component-roles ↩︎

  2. https://stackoverflow.com/questions/66805649/whats-difference-between-kubectl-auth-reconcile-and-kubectl-apply-for-workin ↩︎

  3. https://github.com/kubernetes/kubernetes/issues/103675 ↩︎

  4. https://kubernetes.io/docs/concepts/security/rbac-good-practices/ ↩︎